/**
 * $RCSfile: RTPBridge.java,v $
 * $Revision: 1.1 $
 * $Date: 2007/07/02 17:41:07 $
 *
 * Copyright 2003-2005 Jive Software.
 *
 * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jivesoftware.smackx.jingle.nat;

import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.Iterator;

import org.jivesoftware.smack.PacketCollector;
import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.PacketIDFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.provider.IQProvider;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smackx.ServiceDiscoveryManager;
import org.jivesoftware.smackx.jingle.SmackLogger;
import org.jivesoftware.smackx.packet.DiscoverInfo;
import org.xmlpull.v1.XmlPullParser;

/**
 * RTPBridge IQ Packet used to request and retrieve a RTPBridge Candidates that
 * can be used for a Jingle Media Transmission between two parties that are
 * behind NAT. This Jingle Bridge has all the needed information to establish a
 * full UDP Channel (Send and Receive) between two parties. <i>This transport
 * method should be used only if other transport methods are not allowed. Or if
 * you want a more reliable transport.</i>
 * <p/>
 * High Level Usage Example:
 * <p/>
 * RTPBridge rtpBridge = RTPBridge.getRTPBridge(xmppConnection, sessionID);
 * 
 * @author Thiago Camargo
 */
public class RTPBridge extends IQ {

	private static final SmackLogger LOGGER = SmackLogger
			.getLogger(RTPBridge.class);

	private String sid;
	private String pass;
	private String ip;
	private String name;
	private int portA = -1;
	private int portB = -1;
	private String hostA;
	private String hostB;
	private BridgeAction bridgeAction = BridgeAction.create;

	private enum BridgeAction {

		create, change, publicip
	}

	/**
	 * Element name of the packet extension.
	 */
	public static final String NAME = "rtpbridge";

	/**
	 * Element name of the packet extension.
	 */
	public static final String ELEMENT_NAME = "rtpbridge";

	/**
	 * Namespace of the packet extension.
	 */
	public static final String NAMESPACE = "http://www.jivesoftware.com/protocol/rtpbridge";

	static {
		ProviderManager.getInstance().addIQProvider(NAME, NAMESPACE,
				new Provider());
	}

	/**
	 * Creates a RTPBridge Instance with defined Session ID
	 * 
	 * @param sid
	 */
	public RTPBridge(String sid) {
		this.sid = sid;
	}

	/**
	 * Creates a RTPBridge Instance with defined Session ID
	 * 
	 * @param action
	 */
	public RTPBridge(BridgeAction action) {
		this.bridgeAction = action;
	}

	/**
	 * Creates a RTPBridge Instance with defined Session ID
	 * 
	 * @param sid
	 * @param bridgeAction
	 */
	public RTPBridge(String sid, BridgeAction bridgeAction) {
		this.sid = sid;
		this.bridgeAction = bridgeAction;
	}

	/**
	 * Creates a RTPBridge Packet without Session ID
	 */
	public RTPBridge() {
	}

	/**
	 * Get the attributes string
	 */
	public String getAttributes() {
		StringBuilder str = new StringBuilder();

		if (getSid() != null)
			str.append(" sid='").append(getSid()).append("'");

		if (getPass() != null)
			str.append(" pass='").append(getPass()).append("'");

		if (getPortA() != -1)
			str.append(" porta='").append(getPortA()).append("'");

		if (getPortB() != -1)
			str.append(" portb='").append(getPortB()).append("'");

		if (getHostA() != null)
			str.append(" hosta='").append(getHostA()).append("'");

		if (getHostB() != null)
			str.append(" hostb='").append(getHostB()).append("'");

		return str.toString();
	}

	/**
	 * Get the Session ID of the Packet (usually same as Jingle Session ID)
	 * 
	 * @return
	 */
	public String getSid() {
		return sid;
	}

	/**
	 * Set the Session ID of the Packet (usually same as Jingle Session ID)
	 * 
	 * @param sid
	 */
	public void setSid(String sid) {
		this.sid = sid;
	}

	/**
	 * Get the Host A IP Address
	 * 
	 * @return
	 */
	public String getHostA() {
		return hostA;
	}

	/**
	 * Set the Host A IP Address
	 * 
	 * @param hostA
	 */
	public void setHostA(String hostA) {
		this.hostA = hostA;
	}

	/**
	 * Get the Host B IP Address
	 * 
	 * @return
	 */
	public String getHostB() {
		return hostB;
	}

	/**
	 * Set the Host B IP Address
	 * 
	 * @param hostB
	 */
	public void setHostB(String hostB) {
		this.hostB = hostB;
	}

	/**
	 * Get Side A receive port
	 * 
	 * @return
	 */
	public int getPortA() {
		return portA;
	}

	/**
	 * Set Side A receive port
	 * 
	 * @param portA
	 */
	public void setPortA(int portA) {
		this.portA = portA;
	}

	/**
	 * Get Side B receive port
	 * 
	 * @return
	 */
	public int getPortB() {
		return portB;
	}

	/**
	 * Set Side B receive port
	 * 
	 * @param portB
	 */
	public void setPortB(int portB) {
		this.portB = portB;
	}

	/**
	 * Get the RTP Bridge IP
	 * 
	 * @return
	 */
	public String getIp() {
		return ip;
	}

	/**
	 * Set the RTP Bridge IP
	 * 
	 * @param ip
	 */
	public void setIp(String ip) {
		this.ip = ip;
	}

	/**
	 * Get the RTP Agent Pass
	 * 
	 * @return
	 */
	public String getPass() {
		return pass;
	}

	/**
	 * Set the RTP Agent Pass
	 * 
	 * @param pass
	 */
	public void setPass(String pass) {
		this.pass = pass;
	}

	/**
	 * Get the name of the Candidate
	 * 
	 * @return
	 */
	public String getName() {
		return name;
	}

	/**
	 * Set the name of the Candidate
	 * 
	 * @param name
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * Get the Child Element XML of the Packet
	 * 
	 * @return
	 */
	public String getChildElementXML() {
		StringBuilder str = new StringBuilder();
		str.append("<" + ELEMENT_NAME + " xmlns='" + NAMESPACE + "' sid='")
				.append(sid).append("'>");

		if (bridgeAction.equals(BridgeAction.create))
			str.append("<candidate/>");
		else if (bridgeAction.equals(BridgeAction.change))
			str.append("<relay ").append(getAttributes()).append(" />");
		else
			str.append("<publicip ").append(getAttributes()).append(" />");

		str.append("</" + ELEMENT_NAME + ">");
		return str.toString();
	}

	/**
	 * IQProvider for RTP Bridge packets. Parse receive RTPBridge packet to a
	 * RTPBridge instance
	 * 
	 * @author Thiago Rocha
	 */
	public static class Provider implements IQProvider {

		public Provider() {
			super();
		}

		public IQ parseIQ(XmlPullParser parser) throws Exception {

			boolean done = false;

			int eventType;
			String elementName;
			String namespace;

			if (!parser.getNamespace().equals(RTPBridge.NAMESPACE))
				throw new Exception("Not a RTP Bridge packet");

			RTPBridge iq = new RTPBridge();

			for (int i = 0; i < parser.getAttributeCount(); i++) {
				if (parser.getAttributeName(i).equals("sid"))
					iq.setSid(parser.getAttributeValue(i));
			}

			// Start processing sub-elements
			while (!done) {
				eventType = parser.next();
				elementName = parser.getName();
				namespace = parser.getNamespace();

				if (eventType == XmlPullParser.START_TAG) {
					if (elementName.equals("candidate")) {
						for (int i = 0; i < parser.getAttributeCount(); i++) {
							if (parser.getAttributeName(i).equals("ip"))
								iq.setIp(parser.getAttributeValue(i));
							else if (parser.getAttributeName(i).equals("pass"))
								iq.setPass(parser.getAttributeValue(i));
							else if (parser.getAttributeName(i).equals("name"))
								iq.setName(parser.getAttributeValue(i));
							else if (parser.getAttributeName(i).equals("porta"))
								iq.setPortA(Integer.parseInt(parser
										.getAttributeValue(i)));
							else if (parser.getAttributeName(i).equals("portb"))
								iq.setPortB(Integer.parseInt(parser
										.getAttributeValue(i)));
						}
					} else if (elementName.equals("publicip")) {
						// String p = parser.getAttributeName(0);
						int x = parser.getAttributeCount();
						for (int i = 0; i < parser.getAttributeCount(); i++) {
							if (parser.getAttributeName(i).equals("ip"))
								iq.setIp(parser.getAttributeValue(i));
						}
					}
				} else if (eventType == XmlPullParser.END_TAG) {
					if (parser.getName().equals(RTPBridge.ELEMENT_NAME)) {
						done = true;
					}
				}
			}
			return iq;
		}
	}

	/**
	 * Get a new RTPBridge Candidate from the server. If a error occurs or the
	 * server don�t support RTPBridge Service, null is returned.
	 * 
	 * @param xmppConnection
	 * @param sessionID
	 * @return
	 */
	public static RTPBridge getRTPBridge(XMPPConnection xmppConnection,
			String sessionID) {

		if (!xmppConnection.isConnected()) {
			return null;
		}

		RTPBridge rtpPacket = new RTPBridge(sessionID);
		rtpPacket.setTo(RTPBridge.NAME + "." + xmppConnection.getServiceName());

		PacketCollector collector = xmppConnection
				.createPacketCollector(new PacketIDFilter(rtpPacket
						.getPacketID()));

		xmppConnection.sendPacket(rtpPacket);

		RTPBridge response = (RTPBridge) collector
				.nextResult(SmackConfiguration.getPacketReplyTimeout());

		// Cancel the collector.
		collector.cancel();

		return response;
	}

	/**
	 * Check if the server support RTPBridge Service.
	 * 
	 * @param xmppConnection
	 * @return
	 */
	public static boolean serviceAvailable(XMPPConnection xmppConnection) {

		if (!xmppConnection.isConnected()) {
			return false;
		}

		LOGGER.debug("Service listing");

		ServiceDiscoveryManager disco = ServiceDiscoveryManager
				.getInstanceFor(xmppConnection);
		if (null == disco) {
			disco = new ServiceDiscoveryManager(xmppConnection);
		}
		try {
			// DiscoverItems items =
			// disco.discoverItems(xmppConnection.getServiceName());
			// Iterator iter = items.getItems();
			// while (iter.hasNext()) {
			// DiscoverItems.Item item = (DiscoverItems.Item) iter.next();
			// if (item.getEntityID().startsWith("rtpbridge.")) {
			// return true;
			// }
			// }

			DiscoverInfo discoInfo = disco.discoverInfo(xmppConnection
					.getServiceName());
			Iterator iter = discoInfo.getIdentities();
			while (iter.hasNext()) {
				DiscoverInfo.Identity identity = (DiscoverInfo.Identity) iter
						.next();
				if ((identity.getName() != null)
						&& (identity.getName().startsWith("rtpbridge"))) {
					return true;
				}
			}
		} catch (XMPPException e) {
			e.printStackTrace();
		}

		return false;
	}

	/**
	 * Check if the server support RTPBridge Service.
	 * 
	 * @param xmppConnection
	 * @return
	 */
	public static RTPBridge relaySession(XMPPConnection xmppConnection,
			String sessionID, String pass, TransportCandidate proxyCandidate,
			TransportCandidate localCandidate) {

		if (!xmppConnection.isConnected()) {
			return null;
		}

		RTPBridge rtpPacket = new RTPBridge(sessionID,
				RTPBridge.BridgeAction.change);
		rtpPacket.setTo(RTPBridge.NAME + "." + xmppConnection.getServiceName());
		rtpPacket.setType(Type.SET);

		rtpPacket.setPass(pass);
		rtpPacket.setPortA(localCandidate.getPort());
		rtpPacket.setPortB(proxyCandidate.getPort());
		rtpPacket.setHostA(localCandidate.getIp());
		rtpPacket.setHostB(proxyCandidate.getIp());

		// LOGGER.debug("Relayed to: " + candidate.getIp() + ":" +
		// candidate.getPort());

		PacketCollector collector = xmppConnection
				.createPacketCollector(new PacketIDFilter(rtpPacket
						.getPacketID()));

		xmppConnection.sendPacket(rtpPacket);

		RTPBridge response = (RTPBridge) collector
				.nextResult(SmackConfiguration.getPacketReplyTimeout());

		// Cancel the collector.
		collector.cancel();

		return response;
	}

	/**
	 * Get Public Address from the Server.
	 * 
	 * @param xmppConnection
	 * @return public IP String or null if not found
	 */
	public static String getPublicIP(XMPPConnection xmppConnection) {

		if (!xmppConnection.isConnected()) {
			return null;
		}

		RTPBridge rtpPacket = new RTPBridge(RTPBridge.BridgeAction.publicip);
		rtpPacket.setTo(RTPBridge.NAME + "." + xmppConnection.getServiceName());
		rtpPacket.setType(Type.SET);

		// LOGGER.debug("Relayed to: " + candidate.getIp() + ":" +
		// candidate.getPort());

		PacketCollector collector = xmppConnection
				.createPacketCollector(new PacketIDFilter(rtpPacket
						.getPacketID()));

		xmppConnection.sendPacket(rtpPacket);

		RTPBridge response = (RTPBridge) collector
				.nextResult(SmackConfiguration.getPacketReplyTimeout());

		// Cancel the collector.
		collector.cancel();

		if (response == null)
			return null;

		if (response.getIp() == null || response.getIp().equals(""))
			return null;

		Enumeration ifaces = null;
		try {
			ifaces = NetworkInterface.getNetworkInterfaces();
		} catch (SocketException e) {
			e.printStackTrace();
		}
		while (ifaces != null && ifaces.hasMoreElements()) {

			NetworkInterface iface = (NetworkInterface) ifaces.nextElement();
			Enumeration iaddresses = iface.getInetAddresses();

			while (iaddresses.hasMoreElements()) {
				InetAddress iaddress = (InetAddress) iaddresses.nextElement();
				if (!iaddress.isLoopbackAddress())
					if (iaddress.getHostAddress().indexOf(response.getIp()) >= 0)
						return null;

			}
		}

		return response.getIp();
	}

}
